// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System;
using System.Collections.Generic;
using System.Reflection;
using System.Linq;

namespace SnapObjects.Expressions
{
    public class MethodMetadata<TData, TParam> : Metadata<TData>
    {
        private readonly List<MethodDataInfo<TData, TParam>> _methods;

        public MethodMetadata(string name, MethodDataInfo<TData, TParam> method)
            : base(name, default)
        {
            _methods = new List<MethodDataInfo<TData, TParam>>();

            this.AddMethod(method);
        }

        public MethodMetadata(MethodMetadata<TData, TParam> metadata)
            : base(metadata.Key, default)
        {
            _methods = metadata._methods;
        }

        public override MetadataType Type => MetadataType.Method;
        public IReadOnlyList<MethodDataInfo<TData, TParam>> OverLoads => _methods;

        public virtual void AddMethod(MethodDataInfo<TData, TParam> method)
        {
            this.CheckMethod(method);

            _methods.Add(method);
        }

        public void AddMethods(IEnumerable<MethodDataInfo<TData, TParam>> methods)
        {
            foreach (var method in methods)
            {
                this.AddMethod(method);
            }
        }

        private void CheckMethod(MethodDataInfo<TData, TParam> method)
        {
            foreach (var m in _methods)
            {
                if (m.Parameters.Count == method.Parameters.Count)
                {
                    bool isExists = true;

                    for (int i = 0; i < m.Parameters.Count; i++)
                    {
                        if (m.Parameters[i].Type != method.Parameters[i].Type)
                        {
                            isExists = false;

                            break;
                        }
                    }

                    if (isExists)
                    {
                        throw new Exception(String.Format(Resources.METHOD_EXISTS, method.Name));
                    }
                }
            }
        }
    }

    public class MethodDataInfo<TData, TParam>
    {
        private readonly Func<Type[], MethodDataInfo<TData, TParam>> _makeGeneric;

        public MethodDataInfo(
            string name,
            TData body,
            IReadOnlyList<MethodParamInfo<TParam>> parameters,
            int variableParamIndex = -1,
            int optionalParamIndex = -1)
        {
            this.Name = name;
            this.Body = body;
            this.Parameters = parameters;

            this.VariableParamIndex = variableParamIndex;
            this.OptionalParamIndex = optionalParamIndex;

            this.HasVariableParam = this.VariableParamIndex > -1;
            this.HasOptionalParam = this.OptionalParamIndex > -1;
        }

        public MethodDataInfo(
            MethodInfo method,
            Func<Type[], MethodDataInfo<TData, TParam>> makeGeneric)
        {
            _makeGeneric = makeGeneric;

            this.Name = method.Name;
            this.MethodInfo = method;

            this.IsGenericMethod = true;
        }

        public string Name { get; }
        public TData Body { get; }
        public MethodInfo MethodInfo { get; }
        public IReadOnlyList<MethodParamInfo<TParam>> Parameters { get; }
        public bool IsGenericMethod { get; }
        public bool HasVariableParam { get; }
        public bool HasOptionalParam { get; }
        public int VariableParamIndex { get; }
        public int OptionalParamIndex { get; }

        public MethodDataInfo<TData, TParam> MakeGeneric(params Type[] typeArguments)
        {
            if (this.IsGenericMethod && _makeGeneric != null)
            {
                return _makeGeneric(typeArguments);
            }

            return this;
        }
    }

    public class MethodParamInfo<TParam>
    {
        public MethodParamInfo(
            string name,
            TParam value,
            Type type,
            bool isNotNull,
            bool isOptional = false,
            object defaultOptional = null)
        {
            this.Name = name;
            this.Value = value;
            this.Type = type;
            this.IsNotNull = isNotNull;
            this.IsOptional = isOptional;
            this.Default = defaultOptional;
        }

        public string Name { get; }
        public TParam Value { get; }
        public Type Type { get; }
        public bool IsOptional { get; }
        public object Default { get; }
        public bool IsNotNull { get; set; }
    }
}
